Software and process for play-cursor calculation

ABSTRACT

A process and software for achieving minimal latency in digital audio recording, which includes calculating a repeatable play cursor lead and a play cursor position and writing audio data at the play cursor position plus the play cursor lead.

[0001] This application claims the benefit of the priority date of pending U.S. Provisional Patent Application Serial No. 60/231,470, filed Sep. 8, 2000, which application is incorporated herein by reference.

FIELD OF THE INVENTION

[0002] The invention pertains to the field of computer software programs and processes, and more specifically to the field of computer programs and processes for low rendering latency in telephony and in applications that mix multiple audio streams.

BACKGROUND OF THE INVENTION

[0003] In playback of digital audio on computers, software must write each buffer ahead of the play cursor, such that there is a guarantee that the buffer will be played from start to end, i.e. when the play cursor advances to the location of the buffer. If the buffer is written such that the play cursor is already advanced past the position where the beginning of the buffer, then part of the buffer will not be played and there will be discontinuity in the audio signal. Neither of these conditions is acceptable.

[0004] In conventional architecture, the program typically queries the operating system or audio driver for the current play cursor position. This yields a value that is neither accurate nor precise. In some cases, the driver or operating system adds additional padding, and in some cases the program itself does this step. The result is additional latency, which is undesirable.

[0005] A need exists for a process, preferable a software process, to reduce or eliminate this type of latency in audio playback.

SUMMARY OF THE INVENTION

[0006] In a preferred embodiment, the invention is a software program for initial calibration and highly accurate on-demand calculation of the play cursor position in a computer audio system, for the purpose of digital audio playback.

[0007] In another preferred embodiment, the invention is a process for initial calibration and highly accurate on-demand calculation of the play cursor position in a computer audio system, for the purpose of digital audio playback.

DETAILED DESCRIPTION OF A PREFERRED EMBODIMENT OF THE INVENTION

[0008] The “play cursor” is defined as the position within an audio buffer at which the next read operation by the audio API, device driver, or hardware will occur, regardless if whether to play the sound directly, or to mix the sound into another audio buffer. The play cursor may increment by single samples, or by larger chunks. Chunky cursor position movement may contain jitter.

[0009] “Play cursor lead” is defined as the margin for writing ahead of the reported cursor position. This lead is designed to guarantee that a high-performance audio engine can write ahead of the play cursor.

[0010] The “write cursor position” is defined as the play cursor position plus the play cursor lead.

[0011] “Latency” is defined as the elapsed time between when an audio event is triggered and when it plays from the speakers. Large play cursor leads contribute to higher latencies.

[0012] The invention pertains to low rendering latency in telephony and in applications that mix multiple audio streams. By maintaining an accurate play cursor position a program is able to use a minimal play cursor lead. This provides improved audio playback latency compared to conventional techniques.

[0013] In a preferred embodiment, the invention is part of a software program and a process used with the Windows operating system with the program preferably using the DirectSound APIs.

[0014] In one embodiment, the invention is a process and a software program for capturing and while rendering at the same time for the purpose of measuring and calibrating parameters in order to achieve minimal latency. In another embodiment, the invention is a process and a software program for maintaining reported cursor positions, accurately estimating current position, and writing audio data at a determined safe “lead” relative to the calculated current cursor position.

[0015] First, the program must determine the play cursor lead. Play cursor lead is different for every hardware driver and operating system revision, but it is constant on a given computer configuration.

[0016] The calculation is performed by testing many play cursor leads, to determine the lowest value at which valid output is produced.

[0017] To test for valid output, a short pulse of known duration is written into the playback buffer. Simultaneously, the program captures the digital audio playback. Methods for capturing from playback include, but are not limited to, aiming the microphone at the speakers, connecting the microphone or line-in jack to the speaker or line-out jack, or by configuring the internal mixer in the sound hardware.

[0018] When the captured pulse has the same duration as the written playback pulse, this means that the write position was valid because the pulse was captured in its entirety.

[0019] The difference between the best play cursor position and the reported play cursor position is the play cursor lead. One may elect not to use the best possible play cursor position to allow more safety margin to compensate for high CPU loads, operating system non-determinism, etc.

[0020] Play cursor lead may be positive or negative.

[0021] Once this calibration phase is complete, the program can enter into playback mode. In this mode, the program executes two asynchronous threads. The first thread periodically queries DirectSound for the current play cursor position. It stores the value in a circular array. The purpose of the array is to store a quantity of time-stamped cursor positions over a long period of time, The sampling interval is typically, but not necessarily, on the order of 5 ms, and the array typically, but not necessarily, contains a history of the previous 20 cursor positions.

[0022] The second thread is for writing new streaming audio buffers to the output audio buffer. This thread must know the exact play cursor position, so that it can decide if it must write more digital audio to the play buffer, and if so where and how much.

[0023] In this thread, the array of time-stamped play cursor positions is traversed. Values that lie out of range may optionally be discarded. An instantaneous sampling rate is calculated for each position based on the number of samples of change divided by the elapsed time since the last recorded cursor position. A weighted average of these calculated sampling rates is used to interpolate a current estimated play cursor position.

[0024] Adding the play cursor lead may be done at this time, or it's equally preferable to add it in when the play cursor positions are written into the array.

[0025] With this method, the program has highly accurate play cursor position information. The more accurate this information, the less additional safety margin is required, and thus the lower the achieved latency.

[0026] The process of the invention contains the following steps and the software contains instructions to perform the following steps:

[0027] a) calculate a repeatable play cursor lead,

[0028] b) calculate a current estimate of the play cursor position, and

[0029] c) write audio data at the calculated play cursor position of step plus the calculated play cursor lead.

[0030] Preferably, the process contains the following steps and the software preferably contains instructions to perform the following steps:

[0031] a) calculate a repeatable play cursor lead,

[0032] b) periodically and frequently query for the current reported play cursor position,

[0033] c) maintain a list of time-stamped values of the play cursor positions,

[0034] d) calculate, preferably by interpolation, an accurate current estimate of the play cursor position by weighted average, optionally discarding spurious values, and

[0035] e) write audio data at play cursor position plus lead.

[0036] The above description is not to be construed as limiting the invention, but is an illustration of the invention. The example is described in reference to the Windows operating system. However, the invention is applicable to virtually any current or future operating system including but not limited to Windows 95, Windows 98, Windows CE, Windows Me, Windows NT, Windows 2000, Linux, MacOS, BeOS. The example uses the DirectSound API, but could use virtually any current or future audio API including but not limited to waveOut, Linux sound drivers, etc.

[0037] The program as shown in Exhibit 1 is illustrative of software programs of the invention that may be used in the process of the invention to provide very low-latency digital audio.

[0038] The invention permits the performance of the following functions, which are not to be construed as limiting the invention:

[0039] (1) Determining the error in cursor-position reporting by the audio API and/or operating system

[0040] (2) Establishing the initial play cursor position

[0041] (3) Sampling the play cursor position periodically

[0042] (4) Storing recent historical cursor positions with timestamps of when they occurred

[0043] (5) Interpolating the play cursor position on demand

[0044] (6) Real-time capture of a program's own digital audio playback for the purpose of determining, calibrating, or adjusting the program's playback, record, timing, process, thread, or semaphore parameters, settings, or usage.

[0045] (6) Real-time capture of a program's own digital audio playback for the purpose of determining, calibrating, or adjusting the program's playback, record, timing, process, thread, or semaphore parameters, settings, or usage.

[0046] The software and process of the invention provides many practical applications. For example, the invention may be used in:

[0047] (1) Computer telephony

[0048] (2) Multi-user chat, including games

[0049] (3) Internet radio station broadcast

[0050] (4) Synthesizers and other real-time music applications

[0051] Exhibit 1 is a source code for an exemplary program as described above, which contains the following software components:

[0052] PCLD.C, PCLD.H, RATE.C, and RATE.H

[0053] These software components are included on the two CDs that are submitted with this application, and this material on the CDs is incorporated into this specification by reference.

[0054] Although the above description, including the Exhibit, contains many specificities, they should not be interpreted as limitations on the scope of the invention, but rather as illustrations. One skilled in the art will understand that many variations of the invention are possible and that these variations are to be included within the scope of the invention, as defined in the following claims. Exhibit 1 **************** File: rate.h Tab stops: every 2 columns Project: rate Written: by Erik Lorenzen Purpose: Contains the interface specification for the rate DLL History: EL NB: This file must be kept in sync with rate.DEF EXPORTS section ****************************************************************************** / #ifndef rate_INCLUDE #define rate_INCLUDE /* ** rate should be an actual mesured rate if possible */ void rate_Reset (DWORD pos, DWORD rate, DWORD nbts, DWORD nchn, DWORD len_b) ; /* ** These 3 funtions are re-entrant */ void rate_Add (DWORD pos) ; void rate_GetRolRP (DWORD *pos, DWORD *rate, DWORD *bufcnt) ; void rate_GetCumR (DWORD *rate) ; #endif /*************************************************************************** * File: rate.c Version: 1.00 Tab stops: every 2 columns Project: TSTK Copyright: 1998-2000 DiamondWare, Ltd. All rights reserved. Written: by Erik Lorenzen Purpose: Contains source that attempts to control jitter in play cursor positions History: EL Possible enhacements: 1) use knowledge of output mode to determine if a skip happened and do not use data points from before skip 2) possibly add a data field that starts as 0 and gets added into it the duration (if possible) of all skips and subtract that from the time delta to come up with an accurate position using all avalable samples. The difficulty here is determining when there has been a skip and exactly how long it was. ****************************************************************************** / /*=========================================================================== INCLUDES ===========================================================================*/ #include <windows.h> #include “stddef.h” #include “err.h” #include “dwdefs.h” #include “trace.h” #include “market.i” #include “rate.h” /*=========================================================================== DEFINES ===========================================================================*/ //#pragma message (“Change this back”) #define MAXNUMPTS 20 //this one seems best. . . fast rebuild time when the //system stalls accurate enough for other purposes //#define MAXNUMPTS 200 //original value //#define MAXNUMPTS 6000 #define SETTLETIME 1000 #define MAXDEL 100 #define MINDEL 2 #define HIGHPCT 200 //Some cards like the Yamaha OPL3SAX & SBLive need it : ( #define LOWPCT  50 /* ** CHEAPSEM allows the debugger to gain access to certain portions of this code ** without crashing the system from reentrancy. */ #define CHEAPSEM_OPEN if (++sentrant == 1) #define CHEAPSEM_ELSE else #define CHEAPSEM_PRERET sentrant--; #if (err_DEBUG) static DWORD brkpnt; #define BRKPNT brkpnt = brkpnt; #else #define BRKPNT #endif typedef struct { DWORD nbts ; DWORD nchn ; DWORD rate ; DWORD arate ; DWORD len_b ; DWORD indx ; BOOL used [MAXNUMPTS] ; UINT64 pos [MAXNUMPTS] ; DWORD tstmp [MAXNUMPTS] ; INT32 pdel [MAXNUMPTS] ; DWORD tdel [MAXNUMPTS] ; UINT64 tstpos [MAXNUMPTS] ; } RATE; /*=========================================================================== DATA ===========================================================================*/ static volatile RATE srt; static volatile DWORD sentrant = 0; static DWORD snumpts ; // static number of points to track static DWORD smaxdel ; // maximum time delta to allow for a valid sample static DWORD smindel ; // minimum time delta to allow for a valid sample // These keep track of rate after SETTLETIME static DWORD sstime ; // static start time static BOOL scrdy ; // gets set to TRUE when we begin to accumulate static UINT64 scstime ; // static cumulative settle time static UINT64 scsbc ; // static cumulative byte count // These keep track of static UINT64 scrtime ; // static cumulative rolling time static UINT64 scrbc ; // static cumulative rolling byte count static DWORD sbufcntr; /*=========================================================================== CODE ===========================================================================*/ /*-------------------------------------------------------------------------- ---------------------------------------------------------------------------- Internal-use helper ---------------------------------------------------------------------------- --------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------- Calculate a rolling rate * cursor pos --------------------------------------------------------------------------*/ static void CalcPos (void) { DWORD ttime = timeGetTime ( ) ; DWORD tdel; DWORD tmp; DWORD bcmul = (srt.nbts / 8) * srt.nchn; DWORD x; for (x=0;x<snumpts;x++) { if (srt.used [x] == TRUE) { tdel = dwdefs_DELTA (srt.tstmp [x], ttime, MAXDWORD) ; tmp  = tdel * srt.arate; //Convert Delta in samples tmp /= 1000; //Convert Delta in samples tmp *= bcmul; //Convert samples into bytes srt.tstpos [x] = srt.pos [x] + tmp; //Add original position + delta } } } /*-------------------------------------------------------------------------- --------------------------------------------------------------------------*/ static void GetNormPos (DWORD *pos, DWORD *bufcnt) { DWORD x; UINT64 cpos = 0; DWORD count = 0; for (x=0;x<snumpts;x++) { if (srt.used [x] == TRUE) { cpos += srt.tstpos [x] ; count ++; } } if (count) { cpos /= count; *pos  = (DWORD) (cpos % srt.len_b) ; *bufcnt  = (DWORD) (cpos / srt.len_b) ; } else { *pos = 0; *bufcnt = 0; } } /*-------------------------------------------------------------------------- ---------------------------------------------------------------------------- PUBLIC ---------------------------------------------------------------------------- --------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------- --------------------------------------------------------------------------*/ void rate_Reset (DWORD pos, DWORD rate, DWORD nbts, DWORD nchn, DWORD len_b) { DWORD x; snumpts = MAXNUMPTS; smaxdel = MAXDEL; smindel = MINDEL; sstime = timeGetTime ( ) ; scrdy = FALSE; scstime = 0; scsbc = 0; scrtime = 0; scrbc = 0; sbufcntr = 0; //Setup static fields of the RATE struct srt.nbts = nbts; srt.nchn = nchn; srt.rate = rate; srt.arate = rate; srt.len_b = len_b; srt.indx = 0; srt.used [0] = FALSE; srt.pos [0] = pos; srt.pdel [0] = 0; srt.tstmp [0] = timeGetTime ( ) ; srt.tdel [0] = 0; for (x=1;x<snumpts;x++) { srt.used [x] = FALSE; srt.pos [x] = 0; srt.pdel [x] = 0; srt.tstmp [x] = 0; srt.tdel [x] = 0; } } /*-------------------------------------------------------------------------- --------------------------------------------------------------------------*/ void rate_Add (DWORD pos) { DWORD nidx = srt.indx; DWORD oidx = srt.indx; INT32 delta; DWORD tstmp; DWORD tdel; DWORD tmp; DWORD opos = (DWORD) (srt.pos [oidx] % srt.len_b) ; if (pos >= srt.len_b) { /* ** Has happened using DSOUND with a COMPAQ Armada 3500 w/ an ESS 1869 */ #if (err_DEBUG) TRACE_OUT (“rate_Add: Was given a value greater than the end of the buffer\n”) ; #endif return; } CHEAPSEM_OPEN { //Check to make sure enough time has passed before proccessing call tstmp = timeGetTime ( ) ; tdel = dwdefs_DELTA (srt.tstmp [oidx], tstmp, MAXDWORD) ; if (tdel < smindel) { BRKPNT CHEAPSEM_PRERET return; } else if (tdel > smaxdel) { #if (err_DEBUG) TRACE_OUT (“rate_Add: Large pause in updates (tdel > smaxdel)\n”) ; #endif } /* ** Roll on to next entry in index */ CIRCINC (nidx, 0, snumpts) ; if (pos >= opos) //STD { /* ** If the new position is >= old position then we have a STD advance ** scenario. */ delta = (INT32) pos − opos; #if (err_DEBUG) //TRACE_OUT (“rate.c STD ADV: NORMAL\n”) ; #endif BRKPNT } else { //Determine number of bytes assuming buffer wrap (split buff) delta = (INT32) srt.len_b + pos − opos; #if (err_DEBUG) TRACE_OUT (“rate.c RET ADV:SPLIT BUFF\n”) ; #endif BRKPNT sbufcntr++; } /* ** Add one result to our cummulative count */ if (scrdy == TRUE) { scstime += tdel; scsbc += delta; } else { tmp = dwdefs_DELTA (sstime, tstmp, MAXDWORD) ; if (tmp > SETTLETIME) { scrdy = TRUE; //Set flag to show that we have started to accumulate } } /* ** Subtract one result from our rolling count */ if (srt.used [nidx] == TRUE) { scrtime −= srt.tdel [nidx] ; scrbc −= srt.pdel [nidx] ; srt.used [nidx] = FALSE; } /* ** Update tracking array */ srt.indx = nidx ; //update the index srt.used [nidx] = TRUE ; //Set the used flag srt.pos [nidx] = sbufcntr * srt.len_b + pos ; srt.pdel [nidx] = delta ; //Set the delta srt.tstmp [nidx] = tstmp ; srt.tdel [nidx] = tdel ; /* ** Add one result to our rolling count */ scrtime += srt.tdel [nidx] ; scrbc += srt.pdel [nidx] ; } CHEAPSEM_PRERET } /*-------------------------------------------------------------------------- --------------------------------------------------------------------------*/ void rate_Add2 (DWORD pos) { DWORD nidx = srt.indx; DWORD oidx = srt.indx; INT32 max_b; INT32 min_b; INT32 n; DWORD tstmp; DWORD tdel; DWORD tmp; DWORD opos = (DWORD) (srt.pos [oidx] % srt.len_b) ; if (pos >= srt.len_b) { /* ** Has happened using DSOUND with a COMPAQ Armada 3500 w/ an ESS 1869 */ #if (err_DEBUG >= err_VERBOSE) DERROR (rate_BADPOS, “rate_Add: Was given a value greater than the end of the buffer”) ; #endif return; } CHEAPSEM_OPEN { CIRCINC (nidx, 0, snumpts) ; tstmp = timeGetTime ( ) ; tdel = dwdefs_DELTA (srt.tstmp [oidx], tstmp, MAXDWORD) ; if (tdel < smindel) { //Ignore call without doing anything BRKPNT CHEAPSEM_PRERET return; } //At this time there is no need to worry about a max time delay for //a valid sample // //else if (tdel > smaxdel) //{ // #pragma message (“Add tdel function?”) // BRKPNT //} else { /* ** Subtract one result from our rolling count */ if (srt.used [nidx] == TRUE) { scrtime −= srt.tdel [nidx] ; scrbc −= srt.pdel [nidx] ; srt.used [nidx] = FALSE; } /* ** Determine the maximum nuber of bytes that could have happened since last ** transfer. Set acceptable minimum & maximum values */ max_b = ((INT32) tdel * srt.rate * (srt.nbts / 8) * srt.nchn) / 1000; min_b = max_b; max_b = max_b * HIGHPCT / 100; min_b = min_b * LOWPCT / 100; //#pragma message (“RETROGRADE POS || TIMER HALT, and PLAYBACK PUSE logic disabled”) if (pos >= opos) //STD { /* ** If the new position is >= old position then we have a STD advance ** scenario. */ n = (INT32) pos − opos; #if 1 //if (ABS (n) > max_b) //RETROGRADE POS || TIMER HALT //{ // #if (err_DEBUG) // TRACE_OUT (“rate.c STD ADV: RETROGRADE POS || TIMER HALT\n”) ; // #endif // BRKPNT // goto DONE_UNUSED; //} //else if (ABS (n) < min_b) //PLAYBACK PAUSE //{ // #if (err_DEBUG) // TRACE_OUT (“rate.c STD ADV: PLAYBACK PAUSE\n”) ; // #endif // BRKPNT // goto DONE_UNUSED; //else //NORMAL //{ // #if (err_DEBUG) // TRACE_OUT (“rate.c STD ADV: NORMAL\n”) ; // #endif BRKPNT goto DONE_USE; //} #else BRKPNT goto DONE_USE; #endif } else { /* ** If the new position is < old position then we have a SPLIT BUFFER or ** a RETOGRADE POSITION. A SPLIT BUFFER is when the cursor gets to the end ** of the buffer and starts over. A RETROGRADE BUFFER is when the underlying ** driver gives a new position the is behind a previously reported position. */ //Determine number of bytes assuming buffer wrap (split buff) n = (INT32) srt.len_b + pos − opos; #if 1 if (ABS (n) > max_b) //RETROGRADE POS || TIMER HALT { #if (err_DEBUG) TRACE_OUT (“rate.c RET ADV:RETROGRADE POS || TIMER HALT\n”) ; #endif BRKPNT goto DONE_UNUSED; } else if (ABS (n) < min_b) //PLAYBACK PAUSE { #if (err_DEBUG) TRACE_OUT (“rate.c RET ADV:PLAYBACK PAUSE\n”) ; #endif BRKPNT goto DONE_UNUSED; } else //SPLIT BUFF { #if (err_DEBUG) TRACE_OUT (“rate.c RET ADV:SPLIT BUFF\n”) ; #endif BRKPNT sbufcntr++; goto DONE_USE; } #else BRKPNT goto DONE_USE; #endif } DONE_USE: ; /* ** Add one result to our cummulative count */ if (scrdy == TRUE) { scstime += tdel; scsbc += n; } else { tmp = dwdefs_DELTA (sstime, tstmp, MAXDWORD) ; if (tmp > SETTLETIME) { scrdy = TRUE; } } srt.indx = nidx ; //update the index srt.used [nidx] = TRUE ; //Set the used flag srt.pos [nidx] = sbufcntr * srt.len_b + pos ; srt.pdel [nidx] = n ; //Set the delta srt.tstmp [nidx] = tstmp ; srt.tdel [nidx] = tdel ; /* ** Add one result to our rolling count */ scrtime += srt.tdel [nidx] ; scrbc += srt.pdel [nidx] ; CHEAPSEM_PRERET return; DONE_UNUSED: ; srt.indx = nidx ; //update the index srt.used [nidx] = FALSE ; //Default is fail to use srt.pos [nidx] = sbufcntr * srt.len_b + pos ; srt.pdel [nidx] = 0 ; srt.tstmp [nidx] = tstmp ; srt.tdel [nidx] = tdel ; CHEAPSEM_PRERET return; } } CHEAPSEM_PRERET } /*-------------------------------------------------------------------------- change x from 0 to srt.index? go backwards until we find 1st unused? --------------------------------------------------------------------------*/ void rate_GetRolRP (DWORD *pos, DWORD *rate, DWORD *bufcnt) { static DWORD lrate; static DWORD lpos; static DWORD lbc; UINT64 tmp64; CHEAPSEM_OPEN { /* ** Calculate a rolling rate */ if (scrtime > 0) { tmp64 = ((scrbc * 1000) / (scrtime * (srt.nbts / 8) * srt.nchn)) ; *rate = (DWORD) tmp64; } else { *rate = srt.arate; } /* ** Calculate a rolling position */ CalcPos ( ) ; GetNormPos (pos, bufcnt) ; /* ** Return the data */ lrate = *rate; lpos = *pos; lbc = *bufcnt; } CHEAPSEM_ELSE { *rate = lrate ; *pos = lpos ; *bufcnt = lbc; } CHEAPSEM_PRERET } /*-------------------------------------------------------------------------- // --------------------------------------------------------------------------*/ void rate_GetCumR (DWORD *rate) { static DWORD lrate = 0; UINT64 tmp64; CHEAPSEM_OPEN { if (scstime > 0) { tmp64 = ((scsbc * 1000) / (scstime * (srt.nbts / 8) * srt.nchn)) ; *rate = (DWORD) tmp64; } else { *rate = 0; } lrate = *rate; } CHEAPSEM_ELSE { *rate = lrate; } CHEAPSEM_PRERET } /***************************************************************************** * File: pcld.c Version: 1.00 Project: Tele-STK Copyright: 1999-2000 DiamondWare, Ltd. All rights reserved. Written: by Erik Lorenzen Purpose: Contains the interface specification for pcld.c History: see the .c ****************************************************************************** / #ifndef pcld_INCLUDE #define pcld_INCLUDE #include “dwdefs.h” #include “tstk.h” tstk_RV pcld_Find (BOTH tstk_LTNCY2 *ltncy2) ; tstk_RV pcld_Stop (void) ; #endif /***************************************************************************** * File: pcld.c Version: 1.00 Tab stops: every 2 columns Project: Tele-STK Copyright 1999-2000 DiamondWare, Ltd. All rights reserved. Written: by Erik Lorenzen Purpose: History: EL ****************************************************************************** / /*=========================================================================== TO DO ===========================================================================*/ #pragma message (“TODO: PCLD.C”) #pragma message (“ fix super small chance of NULL CallBack”) #pragma message (“ TODO: make different runs at varying sine rates. . . like” 3k, 3.5k, 2.5k”) /*=========================================================================== INCLUDES ===========================================================================*/ #include <windows.h> #include <stdio.h> #include “market.i” #include “dwdefs.h” #include “err.h” #include “tstk.h” #include “smgr.h” #include “cdsp.h” #include “pcld.h” #include “hmix.h” /*=========================================================================== DEFINES ===========================================================================*/ #define LTNCY_NOISETEST_MS 1000 #define LTNCY_MAXWAIT_MS 5000 #define LTNCY_NUMPINGVERIFIES 4 #define PCLENDCODE 9999 #define TOTALTRIES 3 //number of tries to find the PCL window #define BGNOISETRIES 3 #define BGNOISEMIN 100 #define BGNOISEMAX 30000 #define BGNOISEGETMS 1000 /*=========================================================================== DATA ===========================================================================*/ static volatile UINT32 sstarted = 0; static tstk_LTNCY2 sltncy2; static tstk_LTNCY sltncy; static tstk_LTNCY_CBDATA scbd; static INT32 pclwndw [] = { 200, //DS 100, //DS −100, //Wave −200, //Wave −300, //Wave −400, //Wave 0, //Maybe DS 300, //Maybe DS 400, //Maybe DS 500, //Maybe DS −500, //Maybe Wave −600, //Maybe Wave −700, //Maybe Wave −800, //Maybe Wave −900, //Maybe Wave PCLENDCODE } ; static UINT32 splaydevnum; static UINT32 smastervol; static UINT32 swavevol; static UINT32 srecdevnum; static UINT32 smicvol; /*=========================================================================== CODE ===========================================================================*/ /*-------------------------------------------------------------------------- --------------------------------------------------------------------------*/ static tstk_RV DoCB (tstk_LTNCY2 *ltncy2, tstk_LTNCY_CBDATA *cbd) { /* ** NB: the return value of (callback) ( ) is inverted here */ #if (err_DEBUG) { char tmpc [256] ; sprintf (tmpc, “Phase:%d/%d Total:%d Current:%d Status: %s”, cbd−>total_number_phases, cbd−>curent_phase_number, cbd−>total_pctdone, cbd−>current_phase_pctdone, cbd−>status) ; DERROR (pcld_CALLBACK, tmpc) ; } #endif //Check for NULL, if the callback address is NULL the user doesnt want any if (ltncy2−>callback != NULL) { #if (err_DEBUG) if (cbd−>curent_phase_number > cbd−>total_number_phases) { DERROR (pcld_BADPHASE, “cbd−>curent_phase_number > cbd− >total_number_phases”) ; } if (cbd−>total_pctdone > 100) { DERROR (pcld_BADTTLPERCENT, “cbd−>total_pctdone > 100”) ; } if (cbd−>current_phase _pctdone > 100) { DERROR (pcld_BADPHSPERCENT, “cbd−>current_phase_pctdone > 100”) ; } #endif if (cbd−>total_pctdone > 100) { cbd−>total_pctdone = 100; } if (cbd−>current_phase_pctdone > 100) { cbd−>current_phase_pctdone = 100; } if ((ltncy2−>callback) (cbd) == TRUE) { cbd−>status = NULL; return (tstk_RV_LTNCY_CANCEL) ; } cbd−>status = NULL; return (tstk_RV_SUCCESS) ; } else { cbd−>status = NULL; return (tstk_RV_SUCCESS) ; } } /*-------------------------------------------------------------------------- --------------------------------------------------------------------------*/ static BOOL CheckPing5 (tstk_INIT2  *init2, byte *data, UINT32  datalen_b, UINT32  noise, UINT32  pcl, UINT32 *lms, UINT32  pingms, UINT32  minms, UINT32  maxms, tstk_LTNCY2 *ltncy2, tstk_LTNCY_CBDATA *cbd) { UINT32 pingsam; UINT32 getms = 300; //to keep under 64k of record data, no matter what format UINT32 getsam = dwdefs_MS2SAM (getms, init2−>recfmt.rate) ; UINT32 cursam; UINT32 oldsam; UINT32 starttime; UINT32 nms; UINT32 nsam; UINT32 ssam; UINT32 esam; UINT32 mins = dwdefs_MS2SAM (minms, init2−>recfmt.rate) ; UINT32 maxs = dwdefs_MS2SAM (maxms, init2−>recfmt.rate) ; UINT32 doagain = 0; #if (err_DEBUG) char tmps [256] ; #endif /* ** send ping ** Get latency */ DOAGAIN: ; cdsp_GenPing (&(init2−>playfmt), &(init2−>recfmt), pingms, data, &pingsam) ; tstkll_LtncyPlyClear ( ) ; tstkll_LtncyRecClear ( ) ; /* ** smgr_PlyRawPut says it uses the number samples as its input ** but it really uses number of bytes */ smgr_PlyRawPut (pcl, data, dwdefs_SAM2BYTE (pingsam, init2−>playfmt.bits, init2−>playfmt.chan)) ; /* ** Reset pingsam to recfmt */ pingsam = dwdefs_MS2SAM (pingms, init2−>recfmt.rate) ; cursam = 0; oldsam = 0; starttime = timeGetTime ( ) ; do { //Check time, make sure we dont lock up in the loop if ((timeGetTime ( ) − starttime) >= LTNCY_MAXWAIT_MS) { #if (err_DEBUG) cbd−>status = “TIMEOUT: No Ping Found”; (void) DoCB (ltncy2, cbd) ; #endif return (FALSE) ; } smgr_RecRawGet (&(init2−>recfmt), data, getsam, &cursam) ; ssam = 0; esam = 0; if ((cursam) && (oldsam != cursam)) { oldsam = cursam; if (TRUE == cdsp_FindPing2 (&(init2−>recfmt), data, noise, 0, cursam, &ssam, &esam)) { *lms = dwdefs_SAM2MS (ssam, init2−>recfmt.rate) ; nms = dwdefs_SAM2MS ((esam − ssam), init2−>recfmt.rate) ; nsam = esam − ssam; if ((maxs >= nsam) && (nsam >= mins)) { #if (err_DEBUG) sprintf (tmps, “GOOD Ping Size %u/%usam Found %u/%usam lms %u”, pingms, pingsam, nms, nsam, *lms) ; cbd−>status = tmps; (void) DoCB (ltncy2, cbd) ; #endif tstkll_LtncyPlyClear ( ) ; tstkll_LtncyRecClear ( ) ; return (TRUE) ; } else if (maxs < nsam) { if (doagain < 3) { #if (err_DEBUG) sprintf (tmps, “LONG DOAGAIN Ping Size %u/%usam Found %u/%usam lms %u”, pingms, pingsam, nms, nsam, *lms) ; cbd−>status = tmps; (void) DoCB (ltncy2, cbd) ; #endif tstkll_LtncyPlyClear ( ) ; tstkll_LtncyRecClear ( ) ; return (TRUE) ; doagain++; goto DOAGAIN; } else { #if (err_DEBUG) sprintf (tmps, “LONG FAIL Ping Size %u/%usam Found %u/%usam lms %u”, pingms, pingsam, nms, nsam, *lms) ; cbd−>status = tmps; (void) DoCB (ltncy2, cbd) ; #endif tstkll_LtncyPlyClear ( ) ; tstkll_LtncyRecClear ( ) ; return (FALSE) ; } } else if (cursam >= getsam) //short can only happen if { //all data has been recorded #if (err_DEBUG) sprintf (tmps, “SHORT Ping Size %u/%usam Found %u/%usam lms %u”, pingms, pingsam, nms, nsam, *lms) ; cbd−>status = tmps; (void) DoCB (ltncy2, cbd) ; #endif tstkll_LtncyPlyClear ( ) ; tstkll_LtncyRecClear ( ) ; return (FALSE) ; } } } } while (cursam < getsam) ; #if (err_DEBUG) cbd−>satus = “No Ping Found”; (void) DoCB (ltncy2, cbd) ; #endif tstkll_LtncyPlyClear ( ) ; tstkll_LtncyRecClear ( ) ; return (FALSE) ; } /*-------------------------------------------------------------------------- --------------------------------------------------------------------------*/ static tstk_RV StartTSTK (tstk_LTNCY2 *ltncy2, tstk_LTNCY *ltncy, tstk_LTNCY_CBDATA *cbd) { tstk_RV rv; cbd−>current_phase_pctdone = 0; cbd−>status = “Starting Sound System.”; rv = DoCB (ltncy2, cbd) ; if (rv != tstk_RV_SUCCESS) { return (rv) ; } rv = tstkll_LtncyStart (ltncy2−>init, ltncy2−>init2, ltncy) ; if (rv != tstk_RV_SUCCESS) { cbd−>current_phase_pctdone = 100; cbd−>status = “Sound System Failed to Start.”; (void) DoCB (ltncy2, cbd) ; return (rv) ; } else { cbd−>current_phase_pctdone = 100; cbd−>status = “Sound System Started.”; rv = DoCB (ltncy2, cbd) ; if (rv != tstk_RV_SUCCESS) { return (rv) ; } } return (rv) ; } /*-------------------------------------------------------------------------- --------------------------------------------------------------------------*/ static void StopTSTK (tstk_LTNCY2 *ltncy2, tstk_LTNCY *ltncy, tstk_LTNCY_CBDATA *cbd) { cbd−>current_phase_pctdone = 0; cbd−>status = “Stopping Sound System.”; (void) DoCB (ltncy2, cbd) ; (void) tstkll_LtncyStop (ltncy) ; cbd−>current_phase_pctdone = 100; cbd−>status = “Sound System Stopped.”; (void) DoCB (ltncy2, cbd) ; } /*-------------------------------------------------------------------------- --------------------------------------------------------------------------*/ static tstk_RV MixSet2 (tstk_LTNCY2 *ltncy2, tstk_LTNCY_CBDATA *cbd) { tstk_RV rv = tstk_RV_FAIL; #pragma message (“Do auto volume turn up/down?”) cbd−>current_phase_pctdone = 0; cbd−>status = “Setting mixer for detect.”; (void) DoCB (ltncy2, cbd) ; switch (ltncy2−>ldt) { case tstk_LTNCY_DETECT_TYPE_MIXQUIET: { cbd−>status = “Mixer mode is tstk_LTNCY_DETECT_TYPE_MIXQUIET”; rv = tstkml_MixWaveRec (srecdevnum) ; if (rv == tstk_RV_SUCCESS) { //Save all record line volumes //Select “wave rec” line //**do this first, in case there is a locked line with one //**of the “wave rec” lines //Set all other record devices to 0 volume //Set “wave rec” volume to 50% 32767 //Restore all record volumes //Save all playback line volumes //unmute (select) “master” line //unmute (select) “wave” line //**do this first, in case there is a locked line with one //**of the “master” or “wave” lines //Set all other playback lines to 0 volume //Set “master” volume to 50% 32767 //Set “wave” volume to 50% 32767 //Restore all record volumes //(void) tstkml_MixSetVol (splaydevnum, tstk_MIXCNTRL_MASTER, 0 ) ; //(void) tstkml_MixSetVol (splaydevnum, tstk_MIXCNTRL_WAVE, 50% ) ; //(void) tstkml_MixSetVol (splaydevnum, tstk_MIXCNTRL_PLAY_MIC, 0 ) ; //(void) tstkml_MixSetVol (srecdevnum, tstk_MIXCNTRL_MIC, 0 ) ; //(void) tstkml_MixSetVol (srecdevnum, tstk_MIXCNTRL_WAVEREC, 50% ) ; (void) tstkml_MixSetVol (splaydevnum, tstk_MIXCNTRL_MASTER, 0) ; } break; } case tstk_LTNCY_DETECT_TYPE_MIXLOUD : { cbd−>status = “Mixer mode is tstk_LTNCY_DETECT_TYPE_MIXLOUD”; rv = tstkml_MixWaveRec (srecdevnum) ; if (rv == tstk_RV_SUCCESS) { //(void) tstkml_MixSetVol (splaydevnum, tstk_MIXCNTRL_MASTER, 50% ) ; //(void) tstkml_MixSetVol (splaydevnum, tstk_MIXCNTRL_WAVE, 50% ) ; //(void) tstkml_MixSetVol (srecdevnum, tstk_MIXCNTRL_MIC, 0 ) ; //(void) tstkml_MixSetVol (srecdevnum, tstk_MIXCNTRL_WAVEREC, 50% ) ; } break; } case tstk_LTNCY_DETECT_TYPE_MIC : #if (err_DEBUG == err_RELEASE) default : #endif { cbd−>status = “Mixer mode is tstk_LTNCY_DETECT_TYPE_MIC”; //ignore return. . . if we cant set the mic mode assume it is set //in some cases there may not be a recording mixer at all. . . //like on the Yap Phone. rv = tstk_RV_SUCCESS; (void) tstkml_MixMicRec (srecdevnum) ; //(void) tstkml_MixSetVol (splaydevnum, tstk_MIXCNTRL_MASTER, 50% ) ; //(void) tstkml_MixSetVol (splaydevnum, tstk_MIXCNTRL_WAVE, 50% ) ; //(void) tstkml_MixSetVol (srecdevnum, tstk_MIXCNTRL_MIC, 100% ) ; //(void) tstkml_MixSetVol (srecdevnum, tstk_MIXCNTRL_WAVEREC, 0% ) ; break; } #if (err_DEBUG) err_DEFAULT #endif } if (rv == tstk_RV_SUCCESS) { cbd−>current_phase_pctdone = 50; (void) DoCB (ltncy2, cbd) ; cbd−>current_phase_pctdone = 100; cbd−>status = “Mixer is Set”; (void) DoCB (ltncy2, cbd) ; } else { cbd−>current_phase_pctdone = 100; cbd−>status = “Unable to set desired Mode”; (void) DoCB (ltncy2, cbd) ; } return (rv) ; } /*-------------------------------------------------------------------------- --------------------------------------------------------------------------*/ static tstk_RV MixSave (tstk_LTNCY2 *ltncy2, tstk_LTNCY_CBDATA *cbd) { tstk_RV rv; cbd−>current_phase_pctdone = 0; cbd−>status = “Saving Mixer Settings.”; (void) DoCB (ltncy2, cbd) ; //The mixer devices should always be valid, even if “none” is specified rv = tstkml_MixGetDevNum (&(ltncy2−>init2−>playmix), &splaydevnum) ; if (rv != tstk_RV_SUCCESS) return (rv) ; rv = tstkml_MixGetDevNum (&(ltncy2−>init2−>recmix), &srecdevnum) ; if (rv != tstk_RV_SUCCESS) return (rv) ; //However any particular line does not have to exist rv = tstkml_MixGetVol (splaydevnum, tstk_MIXCNTRL_MASTER, &smastervol) ; //if (rv != tstk_RV_SUCCESS) return (rv) ; rv = tstkml_MixGetVol (splaydevnum, tstk_MIXCNTRL_WAVE, &swavevol) ; //if (rv != tstk_RV_SUCCESS) return (rv) ; rv = tstkml_MixGetVol (srecdevnum, tstk_MIXCNTRL_MIC, &smicvol) ; //if (rv != tstk_RV_SUCCESS) return (rv) ; cbd−>current_phase_pctdone = 100; cbd−>status = “Mixer Settings Saved.”; (void) DoCB (ltncy2, cbd) ; return (tstk_RV _SUCCESS) ; } /*-------------------------------------------------------------------------- --------------------------------------------------------------------------*/ static tstk_RV MixRestore (tstk_LTNCY2 *ltncy2, tstk_LTNCY_CBDATA *cbd) { cbd−>current_phase_pctdone = 0; cbd−>status = “Restoring Mixer Settings.”; (void) DoCB (ltncy2, cbd) ; //ser volumes back to level found at (void) tstkml_MixSetVol (splaydevnum, tstk_MIXCNTRL_MASTER, smastervol) ; (void) tstkml_MixSetVol (splaydevnum, tstk_MIXCNTRL_WAVE, swavevol) ; (void) tstkml_MixSetVol (srecdevnum, tstk_MIXCNTRL_MIC, smicvol) ; //set to mic record (void) tstkml_MixMicRec (srecdevnum) ; cbd−>current_phase_pctdone = 100; cbd−>status = “Mixer Settings Restored.”; (void) DoCB (ltncy2, cbd) ; return (tstk_RV _SUCCESS) ; } /*-------------------------------------------------------------------------- Get Background Noise Level //#pragma message (“Remove for realease”) //#if (1) //#if ((1) && (err_DEBUG)) #if (0) { FILE *fp; fp = fopen (“C:\\noiseok.raw”, “wb”) ; if (fp != NULL) { (void) fwrite (ltncy−>data, ltncy−>datalen_b, 1, fp) ; fclose (fp) ; } } #endif --------------------------------------------------------------------------*/ static tstk_RV GetBackGroundNoise3 (tstk_LTNCY2 *ltncy2, tstk_LTNCY *ltncy, UINT32 *noise, tstk_LTNCY_CBDATA *cbd) { UINT32 count; UINT32 tmp; UINT32 getms = BGNOISEGETMS; UINT32 getsam = dwdefs_MS2SAM (getms, ltncy−>init2.recfmt.rate) ; tstk_RV rv; char tmpc [128] ; cbd−>current_phase_pctdone = 0; cbd−>status = “Getting Background Noise Level.”; rv = DoCB (ltncy2, cbd) ; if (rv != tstk_RV_SUCCESS) { return (rv) ; } *noise = MAXUINT32; //MUST init this for (count=0;count<BGNOISETRIES;count++) { tstkll_LtncyPlyClear ( ) ; tstkll_LtncyRecClear ( ) ; if (tstkll_LtncyRec (ltncy, LTNCY_NOISETEST_MS, LTNCY_MAXWAIT_MS) != tstk_RV_SUCCESS) { cbd−>current_phase_pctdone = 100; cbd−>status = “Failed to Get Background Noise Level.”; rv = DoCB (ltncy2, cbd) ; if (rv != tstk_RV_SUCCESS) { return (rv) ; } return (tstk_RV_FAIL) ; } cdsp_GetVol (&(ltncy−>init2.recfmt), ltncy−>data, getsam−4, &tmp) ; if (*noise > tmp) { *noise = tmp; } } //Check lower limit if (*noise < BGNOISEMIN) //very low minimum, a noise { //threshold of 0 would be no good *noise = BGNOISEMIN; } //Check upper limit if (*noise > BGNOISEMAX) { cbd−>current_phase_pctdone = 99; cbd−>status = “Too Much Background Noise to do a Detect.”; (void) DoCB (ltncy2, cbd) ; cbd−>current_phase_pctdone = 100; sprintf (tmpc, “Found Background Noise Level of %u out of 32767.”, *noise) ; cbd−>status = tmpc; (void) DoCB (ltncy2, cbd) ; return (tstk_RV_LTNCY_TOOMUCHBGNOISE) ; } cbd−>current_phase_pctdone = 100; sprintf (tmpc, “Found Background Noise Level of %u out of 32767.”, *noise) ; cbd−>status = tmpc; rv = DoCB (ltncy2, cbd) ; if (rv != tstk_RV_SUCCESS) { return (rv) ; } return (tstk_RV_SUCCESS) ; } /*-------------------------------------------------------------------------- --------------------------------------------------------------------------*/ static tstk_RV FindPCLWindow3 (tstk_LTNCY2 *ltncy2, tstk_LTNCY *ltncy, IN UINT32 noise, OUT UINT32 *pcl, tstk_LTNCY_CBDATA *cbd) { UINT32 numwindows, pertry, perwin; UINT32 x, y, z; //counters UINT32 lms; //latency in ms tstk_RV rv; char tmpc [64] ; cbd−>current_phase_pctdone = 0; cbd−>status = “Looking for Ping.”; rv = DoCB (ltncy2, cbd) ; if (rv != tstk_RV_SUCCESS) { return (rv) ; } //Find nomber of windows we need to try numwindows = 0; while (pclwndw [numwindows] != PCLENDCODE) { numwindows++; } //Set up stats vars pertry = 100 / TOTALTRIES; perwin = pertry / numwindows; //there are numwindows windows to check, check a max of 3 times cbd−>current_phase_pctdone = 0; for (z=0;z<TOTALTRIES;z++) { for (x=0;x<numwindows;x++) { sprintf (tmpc, “Looking for Ping in window %u, PCL of %d.”, x, pclwndw [x] ) ; cbd−>current_phase_pctdone = (z * pertry) + (x * perwin) ; cbd−>status = tmpc; rv = DoCB (ltncy2, cbd) ; if (rv != tstk_RV_SUCCESS) { return (rv) ; } for (y=0;y<LTNCY_NUMPINGVERIFIES;y++) { if (CheckPing5 (&(ltncy−>init2), ltncy−>data, ltncy−>datalen_b, noise, pclwndw [x], &lms, 50, 10, 65, ltncy2, cbd) == FALSE) { break; } } if (y == LTNCY_NUMPINGVERIFIES) { cbd−>current_phase_pctdone = 100; cbd−>status = “Found Ping.”; rv = DoCB (ltncy2, cbd) ; if (rv != tstk_RV_SUCCESS) { return (rv) ; } *pcl = pclwndw [x] ; return (tstk_RV_SUCCESS) ; } } } cbd−>current_phase_pctdone = 100; cbd−>status = “Could not Find Ping.”; rv = DoCB (ltncy2, cbd) ; if (rv != tstk_RV_SUCCESS) { return (rv) ; } return (tstk_RV_FAIL) ; } /*-------------------------------------------------------------------------- --------------------------------------------------------------------------*/ static tstk_RV MacroTune (tstk_LTNCY2 *ltncy2, tstk_LTNCY *ltncy, UINT32 noise, OUT INT32 *pcl, tstk_LTNCY_CBDATA *cbd) { UINT32 lms; //latency in ms UINT32 nlms; //new latency in ms UINT32 olms; //old latency in ms INT32 tpcl = *pcl; //temp pcl holder UINT32 delta; UINT32 x; char tmp [256] ; tstk_RV rv; cbd−>current_phase_pctdone = 0; cbd−>status = “Macro Tuning.”; rv = DoCB (ltncy2, cbd) ; if (rv != tstk_RV_SUCCESS) { return (rv) ; } // Get a baseline lms olms = 0; for (x=0;x<LTNCY_NUMPINGVERIFIES;x++) { if (CheckPing5 (&(ltncy−>init2), ltncy−>data, ltncy−>datalen_b, noise, tpcl, &lms, 1, 0, 15, ltncy2, cbd) == TRUE) { olms += lms; } else { olms += 300; //pick a number that is bigger than most //but smaller that a 64K buffer at 48Khz } } sprintf (tmp, “Established Baseline LMS of %u.”, olms) ; cbd−>current_phase_pctdone = 0; cbd−>status = &tmp [0] ; rv = DoCB (ltncy2, cbd) ; if (rv != tstk_RV_SUCCESS) { return (rv) ; } for (delta=100;delta>1;delta=delta*2/3) { tpcl = *pcl; tpcl −= delta; nlms = 0; // Check to see if we can still find the ping for (x=0;x<LTNCY_NUMPINGVERIFIES;x++) { if (CheckPing5 (&(ltncy−>init2), ltncy−>data, ltncy−>datalen_b, noise, tpcl, &lms, 1, 0, 15, ltncy2, cbd) == FALSE) { nlms = MAXUINT32; goto NEWDELTA; } nlms += lms; } // Make sure the ping has a shorter latency if (olms <= nlms) { //nlms = MAXUINT32; goto NEWDELTA; } olms = nlms; // Found a ping, assign pcl go back to top subtract delta again // and retry looking for a ping *pcl = tpcl; NEWDELTA: ; cbd−>current_phase_pctdone += 10; sprintf (tmp, “Macro Tuning PCL %d DELTA %u OLMS %u NLMS %u.”, tpcl, delta, olms, nlms) ; cbd−>status = &tmp [0] ; rv = DoCB (ltncy2, cbd) ; if (rv != tstk_RV_SUCCESS) { return (rv) ; } } cbd−>current_phase_pctdone = 100; rv = DoCB (ltncy2, cbd) ; if (rv != tstk_RV_SUCCESS) { return (rv) ; } return (tstk_RV_SUCCESS) ; } /*-------------------------------------------------------------------------- --------------------------------------------------------------------------*/ static tstk_RV FineTune (tstk_LTNCY2 *ltncy2, tstk_LTNCY *ltncy, UINT32 noise, OUT INT32 *pcl, tstk_LTNCY_CBDATA *cbd) { tstk_RV rv; UINT32 lms; //latency in ms UINT32 mlms = 0; //max latency in ms INT32 tpcl = *pcl; //temp pcl holder UINT32 starttime = timeGetTime ( ) ; UINT32 looptime = starttime; char tmpc [256] ; UINT32 x, y; cbd−>current_phase_pctdone = 0; cbd−>status = “Fine Tuning.”; rv = DoCB (ltncy2, cbd) ; if (rv != tstk_RV_SUCCESS) { return (rv) ; } /* ** Get a baseline lms, so we know what to reject */ for (x=0,y=0;x<10;x++) { if (CheckPing5 (&(ltncy−>init2), ltncy−>data, ltncy−>datalen_b, noise, tpcl, &lms, 1, 0, 15, ltncy2, cbd) == TRUE) { y++; mlms += lms; } } if (y < 5) //This makes sure we never / by 0 and that a large number of { //missed pings count againt a small lms y++; mlms += 350; } mlms /= y; //avg the latency mlms += 150; //add 150ms enough so that cards with a wide playback //slew (waveout) will be under it, but less than //what a small buffered card would have at 44k stereo. //Normally High 370's, (when 64k is up) sprintf (tmpc, “Established Maximum LMS of %u.”, mlms) ; cbd−>current_phase_pctdone = 0; cbd−>status = tmpc; rv = DoCB (ltncy2, cbd) ; if (rv != tstk_RV_SUCCESS) { return (rv) ; } /* ** Do upto 30seconds of fine tuneing for a successfull PCL or 120 secods total */ while ((timeGetTime ( ) < (looptime + 30000)) && (timeGetTime ( ) < starttime + 120000))) { cbd−>current_phase_pctdone = (((timeGetTime ( ) − starttime) * 100) / 120000) ; sprintf (tmpc, “Fine Tuning PCL %d.”, tpcl) ; cbd−>status = tmpc; rv = DoCB (ltncy2, cbd) ; if (rv != tstk_RV_SUCCESS) { return (rv) ; } if (CheckPing5 (&(ltncy−>init2), ltncy−>data, ltncy−>datalen_b, noise, tpcl, &lms, 1, 0, 50, ltncy2, cbd) == FALSE) { //Ping failed looptime = timeGetTime ( ) ; tpcl++; } if (lms > mlms) { //latency of ping too high, we must have crossed under the PC //back to the top of the record buffer, reject it looptime = timeGetTime ( ) ; tpcl++; } } *pcl = tpcl; return (tstk_RV_SUCCESS) ; } /*-------------------------------------------------------------------------- ---------------------------------------------------------------------------- PUBLIC ---------------------------------------------------------------------------- --------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------- --------------------------------------------------------------------------*/ tstk_RV pcld_Find (BOTH tstk_LTNCY2 *ltncy2) { tstk_VERS vers; tstk_RV rv; UINT32 noise; INT32 tpcl; if (sstarted) { return (tstk_RV_ALREADYINITTED) ; } sstarted++; memcpy (&sltncy2, ltncy2, sizeof (tstk_LTNCY2)) ; //init scbd scbd.total_number_phases = 9; scbd.total_pctdone = 0; scbd.curent_phase_number = 1; scbd.current_phase_pctdone = 0; scbd.status = “Just starting up”; rv = DoCB (&sltncy2, &scbd) ; if (rv != tstk_RV_SUCCESS) { return (rv) ; } //Send a string copy of the ref's up just to make sure we get a log of it if (tstk_GetVers (&vers) == tstk_RV_SUCCESS) { char tmpc [256] ; sprintf (tmpc, “DLL build:%d %s\nVXD build:%d %s\nWDM build:%d %s”, vers.dllbn, vers.dllts, vers.vxdbn, vers.vxdts, vers.wdmbn, vers.wdmts) ; scbd.status = tmpc; DoCB (&sltncy2, &scbd) ; } else { scbd.status = “Call to tstk_GetVers ( ) failed.”; DoCB (&sltncy2, &scbd) ; } rv = StartTSTK (&sltncy2, &sltncy, &scbd) ; //Try to start TSTK Phase 1, % of total detect 1 if (rv == tstk_RV_SUCCESS) { sltncy2.pcl = sltncy.init2.playdev.stdpcl; //Set pcl to the std latency, if we fail to detect scbd.total_pctdone += 1; scbd.curent_phase_number += 1; rv = MixSave (&sltncy2, &scbd) ; if (rv == tstk_RV_SUCCESS) { scbd.total_pctdone += 1; scbd.curent_phase_number += 1; rv = MixSet2 (&sltncy2, &scbd) ; if (rv == tstk_RV_SUCCESS) { scbd.total_pctdone += 1; scbd.curent_phase_number += 1; rv = GetBackGroundNoise3 (&sltncy2, &sltncy, &noise, &scbd) ; //Phase 3, 5% total if (rv == tstk_RV_SUCCESS) { scbd.total_pctdone += 5; scbd.curent_phase_number += 1; rv = FindPCLWindow3 (&sltncy2, &sltncy, noise, &tpcl, &scbd) ; if (rv == tstk_RV_SUCCESS) { scbd.total_pctdone += 10; scbd.curent_phase_number += 1; rv = MacroTune (&sltncy2, &sltncy, noise, &tpcl, &scbd) ; if (rv ==tstk_RV_SUCCESS) { scbd.total_pctdone += 20; scbd.curent_phase_number += 1; rv = FineTune (&sltncy2, &sltncy, noise, &tpcl, &scbd) ; if (rv == tstk_RV_SUCCESS) { scbd.total_pctdone += 60; scbd.curent_phase_number += 1; tpcl += 10 //add safety margin sltncy2.pcl = tpcl; //Set PCL } } } } //Set to almost done scbd.curent_phase_number = 8; scbd.total_pctdone = 100 − 1; } } MixRestore (&sltncy2, &scbd) ; //Phase 2nd to last (or 7), 1% scbd.curent_phase_number = 9; scbd.total_pctdone = 100; StopTSTK (&sltncy2, &sltncy, &scbd) ; //Phase last (or 8), 1% } //Copy out the return values ltncy2−>pcl = sltncy2.pcl; sstarted--; return (rv) ; } /*-------------------------------------------------------------------------- NB: there is a super small chance of changing the CB address to NULL right after the CB NULL chech happens but right bfore the callback --------------------------------------------------------------------------*/ tstk_RV pcld_Stop (void) { if (sstarted) { sltncy2.callback = NULL; MixRestore (&sltncy2, &scbd) ; StopTSTK (&sltncy2, &sltncy, &scbd) ; return (tstk_RV_SUCCESS) ; } return (tstk_RV_NOTINITTED) ; } 

1. A software program that comprises instructions to a computer to perform the steps of a) calculate a repeatable play cursor lead, b) calculate a current estimate of the play cursor position, and c) write audio data at the calculated play cursor position of step plus the calculated play cursor lead.
 2. The software program of claim 1 which further comprises instructions to periodically query for the current reported play cursor position.
 3. The software program of claim 2 which further comprises instructions to maintain a list of the play cursor positions obtained as a result of the periodic querying.
 4. The software program of claim 3 wherein the estimate of the play cursor position is by averaging the play cursor positions in the list.
 5. A process for reducing latency in digital audio playback comprising a) calculating a repeatable play cursor lead, b) calculating a current estimate of the play cursor position, and c) writing audio data at the calculated play cursor position of step plus the calculated play cursor lead.
 6. The process of claim 5 which further comprises periodically querying for the current reported play cursor position.
 7. The process of claim 6 which further comprises maintaining a list of the play cursor positions obtained as a result of the periodic querying.
 8. The process of claim 7 wherein the estimate of the play cursor position is by averaging the play cursor positions in the list. 